Перейти к основному содержимому

4.02. Циклы

Разработчику Аналитику Тестировщику
Архитектору Инженеру

Циклы

Циклы – повторяемый блок кода. Это не метод, и не функция, это конструкция, которая позволяет выполнять блок кода несколько раз подряд.

Циклы полезны, когда нужно выполнить одну и ту же операцию для большого количества данных или повторять действие до тех пор, пока не будет выполнено определённое условие. Они помогают избежать ручного дублирования кода. Если нужно вывести на экран числа от 1 до 10, мы можем написать цикл, который автоматически переберёт эти числа и выполнит нужную операцию для каждого из них. Это делает программы компактнее и эффективнее.

Типы циклов:

  • Цикл с фиксированным числом повторений - выполняется заранее известное количество раз (к примеру, вывести числа от 1 до 10);
  • Цикл с условием - выполняется до тех пор, пока условие истинно (продолжать вывод данных, пока пользователь не введёт стоп);
  • Цикл для обработки коллекций - проходит по всем элементам списка, массива или другой структуры данных (например, найти сумму всех чисел в списке).

Циклы могут быть и вложенными, то есть один цикл может содержать себя внутри другой.

Аналогично - методы и функции. Функция может вызывать другие функции. Именно такой подход и упрощает написание кода, особенно когда логика становится большой и сложной.

Устройство и классификация циклов

Циклические конструкции реализуют итеративный вычислительный процесс. Формально цикл определяется тройкой:

  1. Инициализация — установка начального состояния итератора;
  2. Условие продолжения — предикат, проверяемый перед (или после) каждой итерации;
  3. Шаг итерации — изменение состояния итератора.

В зависимости от того, когда происходит проверка условия, различают:

  • Циклы с предусловием (while, for в большинстве языков): проверка выполняется до тела цикла;
  • Циклы с постусловием (do...while в C-подобных языках): тело выполняется как минимум один раз, проверка — после.

Отдельно выделяются итераторные циклы высокого уровня (for...of, foreach, for-in, Java for-each, Python for), которые абстрагируют детали управления итератором и работают с интерфейсами перечисления (IEnumerable, Iterable, Iterator, Symbol.iterator и т.п.). Такие циклы неявно управляют состоянием итератора и корректно завершаются при исчерпании последовательности.

Инвариант цикла

Для анализа корректности важно определять инвариант цикла — утверждение, истинное перед входом в цикл, сохраняющееся после каждой итерации и обеспечивающее правильность результата по завершении. Инвариант применяется в доказательствах корректности (например, при сортировке, поиске, вычислении суммы/произведения).

Пример инварианта для суммы элементов массива a[0..n-1]:

После k-й итерации переменная sum содержит сумму a[0] + a[1] + … + a[k-1].

Ресурсные аспекты

  • Временная сложность цикла определяется произведением числа итераций на сложность тела.
  • Пространственная сложность — обычно O(1), если в теле не создаются структуры линейного размера.
  • При вложенных циклах сложность перемножается: два вложенных цикла по n итераций → O(n²).

Важно: в функциональных стилях (например, map, reduce, filter) итерация реализуется через рекурсию (хвостовую — в оптимизируемых случаях) или скрытые циклы в runtime. Это не устраняет циклическую природу вычислений, но переносит ответственность за управление итерацией в библиотеку.

Безопасность и корректность

  • Выход за границы — типичная ошибка при ручном управлении индексом (off-by-one). Итераторные циклы снижают риск.
  • Бесконечный цикл возникает, если условие никогда не становится ложным (например, при ошибке в шаге итерации или некорректном предусловии).
  • В многопоточных сценариях тело цикла, модифицирующее разделяемое состояние, требует синхронизации (мьютексы, атомарные операции, immutable-подход).